#!/usr/bin/env python3

# Copyright (c) 2005-2014 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import mrget
import shutil
import hashlib
import subprocess


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMkdir(path):
        if os.path.islink(path):
            os.remove(path)
            os.mkdir(path)
        elif os.path.isfile(path):
            os.remove(path)
            os.mkdir(path)
        elif os.path.isdir(path):
            pass                        # already exists
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
            os.mkdir(path)
        else:
            os.mkdir(path)              # path does not exist

    @staticmethod
    def forceSymlink(target, link_path):
        if os.path.islink(link_path) and os.readlink(link_path) == target:      # already exist
            return
        Util.forceDelete(link_path)                   # os.symlink won't overwrite anything, so we delete it first
        os.symlink(target, link_path)

    @staticmethod
    def copyFile(source, target, uid, gid):
        subprocess.run(["cp", source, target])
        os.chown(target, uid, gid)

    @staticmethod
    def wgetDownload(url, localFile, bResume, verboseLevel, bCheckCert=True):
        argList = []

        if verboseLevel == 0:
            argList = ["--quiet"]
        elif verboseLevel == 1:
            argList = ["--quiet", "--show-progress"]
        elif verboseLevel == 2:
            argList = ["--show-progress"]
        else:
            assert False

        if bResume:
            argList.append("-c")

        if not bCheckCert:
            argList.append("--no-check-certificate")

        subprocess.run(["wget", "--passive-ftp", *argList, "-O", localFile, url])

    @staticmethod
    def verifyChksum(filepath, digest, digest_algorithm):
        if digest_algorithm == "sha256":
            h = hashlib.sha256()
            with open(filepath, "rb") as f:
                for byte_block in iter(lambda: f.read(4096), b''):
                    h.update(byte_block)
            return h.hexdigest() == digest

        assert False

    @staticmethod
    def checkAndFetchHttp(filepath, myuris, digest, digestAlgorithm, checkFailureMaxTries, verboseLevel, checkCert=True):
        assert len(myuris) >= 1
        assert (digest and digestAlgorithm) or (digest is None and digestAlgorithm is None)

        if digest is None:
            digest = ""
        tempPath = filepath + ".tmp"
        vfailPath = filepath + ".verify_failed"

        checksum_failure_tries = 0
        while True:
            # determine fetch or resume
            # 1: fetch; 2: resume, 3: finished, 4: re-fetch
            if os.path.exists(filepath):
                if digest != "":
                    if Util.verifyChksum(filepath, digest, digestAlgorithm):
                        fetch = 3
                    else:
                        fetch = 4
                else:
                    fetch = 3
            elif os.path.exists(tempPath):
                fetch = 2
            else:
                fetch = 1

            # print message
            if fetch == 1:
                pass
            elif fetch == 2:
                if verboseLevel > 0:
                    print(">>> Resuming fetching...")
            elif fetch == 3:
                if verboseLevel > 0:
                    print(">>> Already fetched.")
                return
            elif fetch == 4:
                if verboseLevel > 0:
                    print(">>> Verify failed! Refetching...")
                os.rename(filepath, vfailPath)
                Util.forceDelete(tempPath)
            else:
                assert False

            # modify URL
            url = myuris[0]
            if url.startswith("http://") or url.startswith("https://") or url.startswith("ftp://"):
                pass
            elif url.startswith("ipfs://"):
                url = url.replace("ipfs://", "mirror://ipfs-gateway/ipfs/")
                url = mrget.target_urls(url)[0]
                assert url.startswith("http://") or url.startswith("https://")
            else:
                assert False

            # run command
            if fetch == 1 or fetch == 4:
                Util.wgetDownload(url, tempPath, False, verboseLevel, checkCert)
            elif fetch == 2:
                Util.wgetDownload(url, tempPath, True, verboseLevel, checkCert)
            else:
                assert False

            # verify digest
            if digest != "":
                if not Util.verifyChksum(tempPath, digest, digestAlgorithm):
                    os.rename(tempPath, vfailPath)
                    if checksum_failure_tries < checkFailureMaxTries:
                        if verboseLevel > 0:
                            print(">>> Verify failed! Refetching...")
                        checksum_failure_tries += 1
                        continue
                    else:
                        print(">>> Verify failed!")
                        return

            # finished, we must jump out of the loop
            os.rename(tempPath, filepath)
            return
